
#include <QDebug>
#include <QSettings>
#include <QSharedData>
#include <QApplication>
#include <QFileDialog>
#include <QFileInfo>
#include <QDir>
#include <QTemporaryDir>
#include <QStandardItemModel>
#include <QRegularExpression>

#include "plugin-config.h"
#include "isomode.h"
#include "ui_isomode.h"

#include "utils.h"
#include "tasks/taskmanager.h"

namespace TaskType {
    enum Type {
        MountISO = 0, UnMountISO, ExtractISO ,
        FoundSquashfs, FoundSquashfsEnd, MountSquashfs, UnMountSquashfs, UnSquashfs,
        PatchAptSandbox, UpdateSourcesList,
        RemoveSavedDir,
        RegisterNSPawn,
        StartTerminalNSPawn, StartTerminalBWrap, StartTerminalDocker, StartTerminal,
        Invalid = -1
    };
};

enum TermType { XTERM, KONSOLE, MATE_TERMINAL };

class ISOModePrivate
{
public:
    ISOModePrivate(ISOMode *parent = nullptr);
    ~ISOModePrivate();

    QString FindSQUASHFS(const QString &iso_mount);
    QString CreateTempDir();
    void RemoveTempDir();

    // Terminal
    TermType DetectTermType();
    QString GetTerminal();
    bool isKylinOS();

    // Check functions
    bool CheckDirPermission(const QString &directory);
    bool CheckDir(const QString &directory, bool check_empty = false);
    bool CheckDocker();
    bool CheckISOFile(const QString &iso_file);
    bool CheckSQUASHFS(const QString &squashfs_file);
    bool CheckRootfs(const QString &rootfs_path);

    // WorkQueue
    void FoundSQUASHFS(bool silence = false);
    void MountISO(bool silence = false);
    void UnMountISO(bool silence = false);
    void ExtractISO(bool silence = false);
    void MountSQUASHFS(bool silence = false);
    void UnMountSQUASHFS(bool silence = false);
    void ExtractSQUASHFS(bool silence = false);
    void PatchAptSandbox(bool silence = false);
    void UpdateSourcesList(bool silence = false);
    void RemoveSavedDir(bool silence = false);
    void RegisterNSPawn(bool silence = false);
    void StartTerminalNSPawn(bool silence = false);
    void StartTerminalBWrap(bool silence = false);
    void StartTerminalDocker(bool silence = false);
    void StartTerminal(bool silence = false);

    QHash<QString, TermType> support_terms;
    ISOMode::SaveMode save_mode;
    bool is_on_build;

    bool iso_file_ok;
    bool project_dir_ok;
    bool squashfs_file_ok;
    bool save_docker_ok;
    bool save_dir_ok;

    QString iso_file;
    QString iso_mount_path;
    QString iso_extract_path;

    QString project_path;
    QString save_path;

    QString squashfs_file;
    QString squashfs_mount_path;
    QString squashfs_extract_path;

    QRegularExpression re_unsquashfs_progress;
    QTemporaryDir *iso_tmpdir;
    TaskManager *taskmanager;
private:
    ISOMode * const q_ptr;
    Q_DECLARE_PUBLIC(ISOMode)
};

ISOModePrivate::ISOModePrivate(ISOMode *parent) : q_ptr(parent)
{
    support_terms.insert(QLatin1String("xterm"), TermType::XTERM);
    support_terms.insert(QLatin1String("konsole"), TermType::KONSOLE);
    // support_terms.insert(QLatin1String("mate-terminal"), TermType::MATE_TERMINAL);
    // support_terms.insert(QLatin1String("mate-terminal.wrapper"), TermType::MATE_TERMINAL);

    save_mode = ISOMode::SaveMode::Directory;
    is_on_build = false;

    iso_file_ok = false;
    project_dir_ok = false;
    squashfs_file_ok = false;
    save_docker_ok = false;
    save_dir_ok = false;

    re_unsquashfs_progress.setPattern(QLatin1String("(?<bar>\\[.*\\]) +(?<current>\\d*)/(?<max>\\d+) +(?<percent>\\d+)%"));
    re_unsquashfs_progress.setPatternOptions(QRegularExpression::CaseInsensitiveOption);

    iso_tmpdir = nullptr;
    taskmanager = new TaskManager(parent);

    ISOMode::connect(taskmanager, &TaskManager::TaskQueueAllDone, parent, &ISOMode::OnTaskQueueAllDone);
    ISOMode::connect(taskmanager, &TaskManager::TaskStarted, parent, &ISOMode::OnTaskStarted);
    ISOMode::connect(taskmanager, &TaskManager::TaskFinished, parent, &ISOMode::OnTaskFinished);
    ISOMode::connect(taskmanager, &TaskManager::TaskError, parent, &ISOMode::OnTaskError);
    ISOMode::connect(taskmanager, &TaskManager::TaskOutput, parent, &ISOMode::OnTaskOutput);
}

ISOModePrivate::~ISOModePrivate()
{
    RemoveTempDir();
}

QString ISOModePrivate::FindSQUASHFS(const QString &iso_mount)
{
    QDir iso(iso_mount);
    QDir casper = iso.filePath(QLatin1String("casper"));
    if (!casper.exists()) {
        casper.setPath(iso.filePath(QLatin1String("live"))); // UOS ?
    }
    QFileInfo squashfs(casper.filePath(QLatin1String("filesystem.squashfs")));
    if (squashfs.exists() && squashfs.isFile()) {
        return squashfs.absoluteFilePath();
    }
    return QString();
}

QString ISOModePrivate::CreateTempDir()
{
    Q_Q(ISOMode);
    if (!iso_tmpdir) {
        iso_tmpdir = new QTemporaryDir();
    }
    if (!iso_tmpdir->isValid()) {
        qCritical().noquote() << q->ui->tableView_logs->adderror(QString(ISOMode::tr("Create temporary dir failed! %1")).arg(iso_tmpdir->errorString()));
        return QString();
    }
    return iso_tmpdir->path();
}

void ISOModePrivate::RemoveTempDir()
{
    if (iso_tmpdir) {
        iso_tmpdir->remove();
        delete iso_tmpdir;
        iso_tmpdir = nullptr;
    }
}

// Terminal
TermType ISOModePrivate::DetectTermType()
{
    QDir bin_dir(QLatin1String("/usr/bin"));
    QString terminal;
    QFileInfo bin_info;

    bin_info.setFile(bin_dir.filePath(QLatin1String("x-terminal-emulator")));
    if (bin_info.exists() && bin_info.isExecutable()) {
        if (bin_info.isSymLink()) {
            QFileInfo term_info(MiscUtils::RealPath(bin_info.absoluteFilePath()));
            terminal = term_info.fileName();
        } else {
            terminal = bin_info.fileName();
        }
        return support_terms.value(terminal, TermType::XTERM);
    }
    return TermType::XTERM;
}

QString ISOModePrivate::GetTerminal()
{
    TermType termtype = DetectTermType();
    QString term = support_terms.key(termtype, QLatin1String("xterm"));

    QDir bin_dir(QLatin1String("/usr/bin"));
    return bin_dir.absoluteFilePath(term);
}

bool ISOModePrivate::isKylinOS()
{
    QString os_release = QLatin1String("/etc/os-release");
    if (QFileInfo::exists(os_release)) {
        QFile file(os_release);
        file.open(QIODevice::ReadOnly);
        QByteArrayList lines = file.readAll().split('\n');
        for (const QByteArray &line : qAsConst(lines)) {
            QByteArrayList items = line.split('=');
            if (items.at(0) == QByteArray("ID")) {
                if (items.at(1) == QByteArray("kylin")) {
                    return true;
                } else {
                    return false;
                }
            }
        }
    }
    return false;
}

// Check functions
bool ISOModePrivate::CheckDirPermission(const QString &directory)
{
    bool have_permissions = false;
    QFileInfo dir(directory);
    if (!have_permissions) {
        have_permissions = dir.permission(QFile::ExeUser | QFile::ReadUser | QFile::WriteUser);
    }
    if (!have_permissions) {
        have_permissions = dir.permission(QFile::ExeGroup | QFile::ReadGroup | QFile::WriteGroup);
    }
    if (!have_permissions) {
        have_permissions = dir.permission(QFile::ExeOther | QFile::ReadGroup | QFile::WriteOther);
    }
    return have_permissions;
}

bool ISOModePrivate::CheckDir(const QString &directory, bool check_empty)
{
    Q_Q(ISOMode);
    bool enabled = true;
    if (enabled && directory.isEmpty()) {
        // qCritical().noquote() << QLatin1String("Checked directory not specified.");
        enabled = false;
    }
    QFileInfo dir(directory);
    if (enabled && dir.exists()) {
        if (enabled && !dir.isDir()) {
            qCritical().noquote() << q->ui->tableView_logs->adderror(QString(ISOMode::tr("[%1] is not a directory.")).arg(directory));
            enabled = false;
        }
        // Check Directory Empty
        if (enabled && check_empty && !dir.dir().entryList(QDir::NoDotAndDotDot).isEmpty()) {
            qCritical().noquote() << q->ui->tableView_logs->adderror(ISOMode::tr("Directory is not empty. Please remove the directory or clear the contents first."));
            enabled = false;
        }
        // Check Permissions
        bool have_permissions = CheckDirPermission(directory);
        if (enabled && !have_permissions) {
            qCritical().noquote() << q->ui->tableView_logs->adderror(ISOMode::tr("You do not have permission to operate the saved directory."));
            enabled = false;
        }
    }
    return enabled;
}

bool ISOModePrivate::CheckDocker()
{
    bool enabled = true;
    return enabled;
}

bool ISOModePrivate::CheckISOFile(const QString &iso_file)
{
    Q_Q(ISOMode);
    bool enabled = true;
    if (enabled && iso_file.isEmpty()) {
        // qWarning().noquote() << q->ui->tableView_logs->adderror(ISOMode::tr("ISO file not specified."));
        enabled = false;
    }
    QFileInfo iso(iso_file);
    if (enabled && !iso.exists()) {
        qWarning().noquote() << q->ui->tableView_logs->adderror(ISOMode::tr("ISO file not exist."));
        enabled = false;
    }
    if (enabled && !iso.isFile()) {
        qWarning().noquote() << q->ui->tableView_logs->adderror(ISOMode::tr("ISO file not a file."));
        enabled = false;
    }
    return enabled;
}

bool ISOModePrivate::CheckSQUASHFS(const QString &squashfs_file)
{
    Q_Q(ISOMode);
    bool enabled = true;
    if (enabled && squashfs_file.isEmpty()) {
        qCritical().noquote() << q->ui->tableView_logs->adderror(ISOMode::tr("SQUASHFS file not found."));
        enabled = false;
    }
    QFileInfo squashfs(squashfs_file);
    if (enabled && !squashfs.exists()) {
        qCritical().noquote() << q->ui->tableView_logs->adderror(ISOMode::tr("SQUASHFS file not exist."));
        enabled = false;
    }
    if (enabled && !squashfs.isFile()) {
        qCritical().noquote() << q->ui->tableView_logs->adderror(ISOMode::tr("SQUASHFS file not a file."));
        enabled = false;
    }
    if (enabled && !squashfs.permission(QFile::ReadUser | QFile::ReadGroup | QFile::ReadOwner)) {
        qCritical().noquote() << q->ui->tableView_logs->adderror(ISOMode::tr("You do not have permission to read the SQUASHFS file."));
        enabled = false;
    }
    return enabled;
}

bool ISOModePrivate::CheckRootfs(const QString &rootfs_path)
{
    bool is_valid = false;
    QDir dir(rootfs_path);
    QDir bin = MiscUtils::RealPath(dir.filePath(QLatin1String("bin")));
    QFileInfo bash = bin.filePath(QLatin1String("bash"));
    if (bash.exists() && bash.isExecutable()) {
        is_valid = true;
    }

    return is_valid;
}

// WorkQueue
void ISOModePrivate::FoundSQUASHFS(bool silence)
{
    QSharedPointer<TaskData> success_task = QSharedPointer<TaskData>(new TaskData(TaskType::FoundSquashfsEnd));
    success_task->silence = silence;
    success_task->name = ISOMode::tr("UnMount ISO file");
    success_task->exec = QLatin1String("fusermount");
    success_task->args.append(QLatin1String("-u"));
    success_task->args.append(iso_mount_path);

    TaskData task(TaskType::FoundSquashfs);
    task.silence = silence;
    task.name = ISOMode::tr("Mount ISO file");
    task.exec = QLatin1String("fuseiso");
    task.args.append(iso_file);
    task.args.append(iso_mount_path);
    task.success_task = success_task;

    taskmanager->CreateTask(task, false);
}

void ISOModePrivate::MountISO(bool silence)
{
    TaskData task(TaskType::MountISO);
    task.silence = silence;
    task.name = ISOMode::tr("Mount ISO file");
    task.exec = QLatin1String("fuseiso");
    task.args.append(iso_file);
    task.args.append(iso_mount_path);

    taskmanager->AttachTaskQueue(task);
}

void ISOModePrivate::UnMountISO(bool silence)
{
    TaskData task(TaskType::UnMountISO);
    task.silence = silence;
    task.name = ISOMode::tr("UnMount ISO file");
    task.exec = QLatin1String("fusermount");
    task.args.append(QLatin1String("-u"));
    task.args.append(iso_mount_path);

    taskmanager->AttachTaskQueue(task);
}

void ISOModePrivate::ExtractISO(bool silence)
{
    Q_UNUSED(silence)
}

void ISOModePrivate::MountSQUASHFS(bool silence)
{
    TaskData task(TaskType::MountSquashfs);
    task.silence = silence;
    task.name = ISOMode::tr("Mount SQUASHFS file");
    task.exec = QLatin1String("squashfuse");
    task.args.append(iso_file);
    task.args.append(squashfs_mount_path);

    taskmanager->AttachTaskQueue(task);
}

void ISOModePrivate::UnMountSQUASHFS(bool silence)
{
    TaskData task(TaskType::UnMountSquashfs);
    task.silence = silence;
    task.name = ISOMode::tr("UnMount SQUASHFS file");
    task.exec = QLatin1String("fusermount");
    task.args.append(QLatin1String("-u"));
    task.args.append(squashfs_mount_path);

    taskmanager->AttachTaskQueue(task);
}

void ISOModePrivate::ExtractSQUASHFS(bool silence)
{
    QSharedPointer<TaskData> error_task = QSharedPointer<TaskData>(new TaskData(TaskType::UnMountISO));
    error_task->name = ISOMode::tr("UnMount ISO file");
    error_task->exec = QLatin1String("fusermount");
    error_task->args.append(QLatin1String("-u"));
    error_task->args.append(iso_mount_path);

    TaskData task(TaskType::UnSquashfs);
    task.silence = silence;
    task.name = ISOMode::tr("Extract SQUASHFS file");
    task.exec = QLatin1String("fakeroot");
    task.args.append(QLatin1String("unsquashfs"));
    task.args.append(QLatin1String("-quiet"));
    task.args.append(QLatin1String("-force"));
    task.args.append(QLatin1String("-no-xattrs"));
    task.args.append(QLatin1String("-ignore-errors"));
    task.args.append(QLatin1String("-dest"));
    task.args.append(save_path);
    task.args.append(squashfs_file);
    task.error_task = error_task;
    taskmanager->AttachTaskQueue(task);
}

void ISOModePrivate::PatchAptSandbox(bool silence)
{
    // NOTE: APT 安装失败问题解决方法：https://stackoverflow.com/questions/60042416/getting-permission-denied-even-as-root-inside-the-docker-container
    QDir apt_conf(QString(QLatin1String("%1/etc/apt/apt.conf.d")).arg(save_path));
    QString sandbox_disable = apt_conf.absoluteFilePath(QLatin1String("00sandbox-disable"));

    TaskData task(TaskType::PatchAptSandbox);
    task.silence = silence;
    task.name = ISOMode::tr("Update extracted files");
    task.exec = QLatin1String("bash");
    task.args.append(QLatin1String("-c"));
    task.args.append(QString(QLatin1String("echo -e \"APT::Sandbox::User \\\"root\\\";\" > \"%1\"")).arg(sandbox_disable));
    taskmanager->AttachTaskQueue(task);
}

void ISOModePrivate::UpdateSourcesList(bool silence)
{
    QDir apt(QString(QLatin1String("%1/etc/apt")).arg(save_path));
    QString main_sources = apt.absoluteFilePath(QLatin1String("sources.list"));
    QDir apt_sources_d(QString(QLatin1String("%1/etc/apt/sources.list.d")).arg(save_path));
    QString kylin_sources = apt_sources_d.absoluteFilePath(QLatin1String("kylin-sources.list"));

    Q_UNUSED(apt)
    Q_UNUSED(main_sources)

    QStringList sources;
    sources << QLatin1String("deb http://archive.kylinos.cn/kylin/KYLIN-ALL 10.1 main restricted universe multiverse");
    sources << QLatin1String("deb http://archive.kylinos.cn/kylin/KYLIN-ALL 10.1-2403-updates main restricted universe multiverse");
    sources << QLatin1String("deb http://archive.kylinos.cn/kylin/KYLIN-ALL 10.1-2403-update1 main restricted universe multiverse");
    sources << QLatin1String("deb http://archive.kylinos.cn/kylin/KYLIN-ALL 10.1-2403-update2 main restricted universe multiverse");
    sources << QLatin1String("deb http://archive.kylinos.cn/kylin/KYLIN-ALL 10.1-2403-hwe-updates main");
    sources << QLatin1String("deb http://archive.kylinos.cn/kylin/KYLIN-ALL 10.1-2403-hwe-update1 main");
    sources << QLatin1String("deb http://archive.kylinos.cn/kylin/KYLIN-ALL 10.1-2403-hwe-update2 main");
    sources << QLatin1String("deb http://archive2.kylinos.cn/deb/kylin/production/PART-V10-SP1/custom/partner/V10-SP1 default all");

    TaskData task(TaskType::UpdateSourcesList);
    task.silence = silence;
    task.name = ISOMode::tr("Update Sources List");
    task.exec = QLatin1String("bash");
    task.args.append(QLatin1String("-c"));
    task.args.append(QString(QLatin1String("echo -e \"%2\" > \"%1\"")).arg(kylin_sources, sources.join(QLatin1String("\n"))));
    taskmanager->AttachTaskQueue(task);
}

void ISOModePrivate::RemoveSavedDir(bool silence)
{
    TaskData task(TaskType::RemoveSavedDir);
    task.silence = silence;
    task.name = ISOMode::tr("Remove saved directory");
    task.exec = QLatin1String("fakeroot");
    task.args.append(QLatin1String("rm"));
    task.args.append(QLatin1String("-rf"));
    task.args.append(save_path);
    taskmanager->CreateTask(task);
}

void ISOModePrivate::RegisterNSPawn(bool silence)
{
    // NOTE: 参考：https://www.jinbuguo.com/systemd/systemd.index.html
    TaskData task(TaskType::RegisterNSPawn);
    task.silence = silence;
    task.name = ISOMode::tr("Register Environment");
    task.exec = QLatin1String("systemd-nspawn");
    task.args.append(save_path);
}

void ISOModePrivate::StartTerminalNSPawn(bool silence)
{
    Q_UNUSED(silence)
    // NOTE: 参考：https://blog.sww.moe/post/nspawn-is-great
}

void ISOModePrivate::StartTerminalBWrap(bool silence)
{
    Q_Q(ISOMode);
    // NOTE: 非 ROOT 用户使用，参考 bwrap 工具建立环境: https://blog.lilydjwg.me/2021/8/12/using-bwrap.215869.html
    /*
    * unshare -r // 可选
    * bwrap
    *   --unshare-all
    *   --share-net
    *   --cap-add ALL
    *   --die-with-parent
    *   --dev-bind rootfs /
    *   --ro-bind /run /run
    *   --proc /proc
    *   --dev /dev
    *   --tmpfs /dev/shm
    *   --clearenv
    *   --setenv LS_COLORS "$LS_COLORS"
    *   --setenv COLORTERM "$COLORTERM"
    *   --setenv TERM "$TERM"
    *   --setenv SHELL "/bin/bash"
    *   --setenv HOME /root
    *   --setenv USER root
    *   --uid 0
    *   --gid 0
    *   /bin/bash
    */

    QString bwrap_cmd;
    QStringList bwrap_cmd_items;
    bwrap_cmd_items.append(QLatin1String("/usr/bin/bwrap"));
    bwrap_cmd_items.append(QLatin1String("--unshare-all"));
    bwrap_cmd_items.append(QLatin1String("--share-net"));
    bwrap_cmd_items.append(QLatin1String("--die-with-parent"));
    bwrap_cmd_items.append(QString(QLatin1String("--dev-bind \"%1\" /")).arg(save_path));
    bwrap_cmd_items.append(QLatin1String("--ro-bind /run /run"));
    bwrap_cmd_items.append(QLatin1String("--proc /proc"));
    bwrap_cmd_items.append(QLatin1String("--dev /dev"));
    bwrap_cmd_items.append(QLatin1String("--tmpfs /dev/shm"));
    if (project_dir_ok) {
        bwrap_cmd_items.append(QString(QLatin1String("--bind \"%1\" /workdir")).arg(project_path));
        bwrap_cmd_items.append(QString(QLatin1String("--chdir /workdir")).arg(project_path));
    }
    bwrap_cmd_items.append(QLatin1String("--uid 0"));
    bwrap_cmd_items.append(QLatin1String("--gid 0"));
    if (!isKylinOS()) {
        bwrap_cmd_items.append(QLatin1String("--clearenv"));
    }
    bwrap_cmd_items.append(QLatin1String("--setenv SHELL /bin/bash"));
    bwrap_cmd_items.append(QLatin1String("--setenv PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"));
    bwrap_cmd_items.append(QLatin1String("--setenv TERM xterm-256color"));
    bwrap_cmd_items.append(QLatin1String("--setenv USER root"));
    bwrap_cmd_items.append(QLatin1String("--setenv HOME /root"));
    bwrap_cmd_items.append(QLatin1String("/bin/bash"));
    bwrap_cmd = bwrap_cmd_items.join(QLatin1String(" "));
    // qDebug().noquote() << "BWRAP:" << bwrap_cmd;

    TaskData task(TaskType::StartTerminalBWrap);
    task.silence = silence;
    task.name = ISOMode::tr("Start BWRAP Terminal");
    task.exec = GetTerminal();
    qDebug().noquote() << q->ui->tableView_logs->addnormal(QString(ISOMode::tr("Terminal: %1")).arg(task.exec));
    switch (DetectTermType()) {
    case TermType::XTERM:
        break;
    case TermType::KONSOLE:
        // task.args.append(QLatin1String("--hold"));
        break;
    case TermType::MATE_TERMINAL:
        task.args.append(QLatin1String("--window"));
        break;
    default:
        break;
    }
    task.args.append(QLatin1String("-e"));
    task.args.append(bwrap_cmd);
    taskmanager->CreateTask(task);
}

void ISOModePrivate::StartTerminalDocker(bool silence)
{
    Q_UNUSED(silence)
}

void ISOModePrivate::StartTerminal(bool silence)
{
    Q_UNUSED(silence)
}

ISOMode::ISOMode(QWidget *parent) : QWidget(parent), ui(new Ui::ISOMode), d_ptr(new ISOModePrivate(this))
{
    ui->setupUi(this);
    if (true) {
        ui->lineEdit_save_dir->setText(QDir::home().filePath(QLatin1String(PLUGIN_SAVE_DIRNAME)));
    } else {
        ui->lineEdit_save_dir->setText(QDir(QLatin1String("/dev/shm")).filePath(QLatin1String(PLUGIN_SAVE_DIRNAME)));
    }
    ui->pushButton_build->setEnabled(false);
    ui->pushButton_open_term->setEnabled(false);

    // Logic not completion
    ui->widget_save_to->setEnabled(false);

    connect(ui->pushButton_os_image_browse, &QPushButton::clicked, this, &ISOMode::OnISOBrowseClicked);
    connect(ui->pushButton_project_dir, &QPushButton::clicked, this, &ISOMode::OnProjectDirBrowseClicked);
    connect(ui->pushButton_save_dir_browse, &QPushButton::clicked, this, &ISOMode::OnSaveDirBrowseClicked);
    connect(ui->lineEdit_os_image, &QLineEdit::editingFinished, this, &ISOMode::OnISOTextEditingFinished);
    connect(ui->lineEdit_project_dir, &QLineEdit::editingFinished, this, &ISOMode::OnProjectDirTextEditingFinished);
    connect(ui->lineEdit_save_dir, &QLineEdit::editingFinished, this, &ISOMode::OnSaveDirTextEditingFinished);
    connect(ui->radioButton_save_to_dir, &QRadioButton::toggled, this, &ISOMode::OnSaveModeToggle);
    connect(ui->radioButton_save_to_docker, &QRadioButton::toggled, this, &ISOMode::OnSaveModeToggle);
    connect(ui->pushButton_log_clear, &QPushButton::clicked, this, &ISOMode::OnLogClearClicked);
    connect(ui->pushButton_save_clear, &QPushButton::clicked, this, &ISOMode::OnSaveClearClicked);
    connect(ui->pushButton_build, &QPushButton::clicked, this, &ISOMode::OnBuildClicked);
    connect(ui->pushButton_open_term, &QPushButton::clicked, this, &ISOMode::OnOpenTerminalClicked);

    Q_D(ISOMode);
    d->save_path = ui->lineEdit_save_dir->text();

    UpdateUI();
}

ISOMode::~ISOMode()
{
    delete ui;

    Q_D(ISOMode);
    d->taskmanager->StopTaskQueue(false, true);
}

void ISOMode::changeEvent(QEvent *e)
{
    QWidget::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        ui->retranslateUi(this);
        break;
    default:
        break;
    }
}

// Slots
void ISOMode::UpdateUI()
{
    Q_D(ISOMode);
    bool rootfs_valid = d->CheckRootfs(d->save_path);
    if (d->is_on_build) {
        ui->pushButton_build->setText(tr("&Cancel"));
    } else {
        ui->pushButton_build->setText(tr("&Build"));
    }

    bool build_enabled = true;
    build_enabled = build_enabled && d->iso_file_ok;
    build_enabled = build_enabled && d->squashfs_file_ok;
    build_enabled = build_enabled && d->project_dir_ok;
    switch (d->save_mode) {
    case ISOMode::SaveMode::Directory:
        build_enabled = build_enabled && d->save_dir_ok;
        break;
    case ISOMode::SaveMode::Docker:
        build_enabled = build_enabled && d->save_docker_ok;
        break;
    default:
        break;
    }
    ui->pushButton_build->setEnabled(build_enabled);

    ui->pushButton_save_clear->setEnabled(rootfs_valid);
    ui->pushButton_open_term->setEnabled(rootfs_valid);
}

void ISOMode::OnLogClearClicked()
{
    ui->tableView_logs->clear();
}

void ISOMode::OnISOBrowseClicked()
{
    QString iso_file = QFileDialog::getOpenFileName(this, tr("Select a ISO file"), QDir::homePath(), tr("ISO Files (*.iso)"), nullptr, QFileDialog::DontResolveSymlinks);
    ui->lineEdit_os_image->setText(iso_file);
    OnISOTextEditingFinished();
}

void ISOMode::OnISOTextEditingFinished()
{
    Q_D(ISOMode);
    QString text = ui->lineEdit_os_image->text();
    if (text.startsWith(QLatin1String("~"))) {
        text.replace(0, 1, QDir::homePath());
    }
    QFileInfo info(text);
    if (d->iso_file == info.absoluteFilePath() && d->iso_file_ok) {
        return;
    }
    d->iso_file = info.absoluteFilePath();
    d->iso_file_ok = d->CheckISOFile(d->iso_file);

    UpdateUI();

    if (!d->iso_file_ok) {
        return;
    }

    // NOTE: 调整临时目录(tmpdir)的生命周期管理逻辑
    if (d->iso_mount_path.isEmpty()) {
        d->iso_mount_path = d->CreateTempDir();
    }
    QFileInfo dir(d->iso_mount_path);
    if (!dir.exists() || !dir.isDir()) {
        qCritical().noquote() << ui->tableView_logs->adderror(QString(tr("Mount target [%1] is invalid.")).arg(d->iso_mount_path));
        return;
    }
    qDebug().noquote() << ui->tableView_logs->addnormal(QString(tr("ISO file: %1")).arg(d->iso_file));
    qDebug().noquote() << ui->tableView_logs->addnormal(QString(tr("ISO mount to: %1")).arg(d->iso_mount_path));

    // Found SQUASHFS in a task;
    d->FoundSQUASHFS(true);

    UpdateUI();

    if (d->save_mode == ISOMode::SaveMode::Directory) {
        OnSaveDirTextEditingFinished();
    }
}

void ISOMode::OnProjectDirBrowseClicked()
{
    QString default_dir = QDir::homePath();
    QFileInfo info(ui->lineEdit_project_dir->text());
    QDir base_dir = info.dir();
    if (base_dir.exists()) {
        default_dir = base_dir.absolutePath();
    }
    QString project_dir = QFileDialog::getExistingDirectory(this, tr("Select Project Directory"), default_dir, QFileDialog::DontResolveSymlinks|QFileDialog::ShowDirsOnly);
    if (!project_dir.isEmpty()) {
        ui->lineEdit_project_dir->setText(project_dir);
    }
    OnProjectDirTextEditingFinished();
}

void ISOMode::OnProjectDirTextEditingFinished()
{
    Q_D(ISOMode);
    QString text = ui->lineEdit_project_dir->text();
    if (text.startsWith(QLatin1String("~"))) {
        text.replace(0, 1, QDir::homePath());
    }
    QFileInfo info(text);
    if (d->project_path == info.absoluteFilePath() && d->project_dir_ok) {
        return;
    }
    d->project_path = info.absoluteFilePath();
    d->project_dir_ok = d->CheckDir(d->project_path);

    UpdateUI();

    if (!d->project_dir_ok) {
        return;
    }
    qDebug().noquote() << ui->tableView_logs->addnormal(QString(tr("Project directory: %1")).arg(d->project_path));
}

void ISOMode::OnSaveDirBrowseClicked()
{
    QString default_dir = QDir::homePath();
    QFileInfo info(ui->lineEdit_save_dir->text());
    QDir base_dir = info.dir();
    if (base_dir.exists()) {
        default_dir = base_dir.absolutePath();
    }
    QString save_dir = QFileDialog::getExistingDirectory(this, tr("Select Save Directory"), default_dir, QFileDialog::DontResolveSymlinks|QFileDialog::ShowDirsOnly);
    if (!save_dir.isEmpty()) {
        ui->lineEdit_save_dir->setText(save_dir);
    }
    OnSaveDirTextEditingFinished();
}

void ISOMode::OnSaveDirTextEditingFinished()
{
    Q_D(ISOMode);
    QString text = ui->lineEdit_save_dir->text();
    if (text.startsWith(QLatin1String("~"))) {
        text.replace(0, 1, QDir::homePath());
    }
    QFileInfo info(text);
    if (d->save_path == info.absoluteFilePath() && d->save_dir_ok) {
        return;
    }
    d->save_path = info.absoluteFilePath();
    d->save_dir_ok = d->CheckDir(d->save_path);

    UpdateUI();

    if (!d->save_dir_ok) {
        return;
    }

    qDebug().noquote() << ui->tableView_logs->addnormal(QString(tr("Save directory: %1")).arg(d->save_path));
}

void ISOMode::OnSaveModeToggle(bool toggle)
{
    Q_D(ISOMode);
    if (toggle) {
        if (sender() == ui->radioButton_save_to_dir) {
            d->save_mode = ISOMode::SaveMode::Directory;
            ui->widget_save_to_dir->setEnabled(true);
        }
        if (sender() == ui->radioButton_save_to_docker) {
            d->save_mode = ISOMode::SaveMode::Docker;
            ui->widget_save_to_dir->setEnabled(false);
        }
    }
}

void ISOMode::OnSaveClearClicked()
{
    Q_D(ISOMode);
    switch (d->save_mode) {
    case SaveMode::Docker:
        break;
    case SaveMode::Directory:
        d->RemoveSavedDir();
        break;
    default:
        break;
    }
    ui->pushButton_save_clear->setEnabled(false);
}

void ISOMode::OnBuildClicked()
{
    Q_D(ISOMode);
    if (!d->is_on_build) {
        d->MountISO();
        d->ExtractSQUASHFS();
        d->UnMountISO();
        d->PatchAptSandbox();
        d->taskmanager->StartTaskQueue();
        d->is_on_build = true;
    } else {
        d->taskmanager->StopTaskQueue();
        d->is_on_build = false;
    }
    UpdateUI();
}

void ISOMode::OnOpenTerminalClicked()
{
    Q_D(ISOMode);
    if (!d->is_on_build) {
        d->StartTerminalBWrap();
    }
}

// Tasks
void ISOMode::OnTaskQueueAllDone(bool success)
{
    Q_D(ISOMode);
    qDebug().noquote() << __FUNCTION__ << "Success:" << success;
    d->is_on_build = false;
    UpdateUI();
}

void ISOMode::OnTaskStarted(const TaskData &data)
{
    qDebug().noquote() << "Task Started:" << data.name << data.type;
    switch (data.type) {
    case TaskType::RemoveSavedDir:
    case TaskType::UnSquashfs:
    {
        if (!data.silence) {
            qDebug().noquote() << ui->tableView_logs->updateprogress(0, data.name);
        } else {
            qDebug().noquote() << data.name;
        }
        break;
    }
    default: break;
    }
}

void ISOMode::OnTaskFinished(const TaskData &data, int exitcode, const QString &errstr)
{
    Q_D(ISOMode);
    qDebug().noquote() << __FUNCTION__ << data.uuid << data.name << ":::" << errstr << ":::";

    if (exitcode == 0) {
        switch (data.type) {
        case TaskType::FoundSquashfs:
        {
            d->squashfs_file = d->FindSQUASHFS(d->iso_mount_path);
            d->squashfs_file_ok = d->CheckSQUASHFS(d->squashfs_file);
            if (d->squashfs_file_ok) {
                qDebug().noquote() << ui->tableView_logs->addnormal(QString(tr("SQUASHFS file: %1")).arg(d->squashfs_file));
            }
            UpdateUI();
            break;
        }
        case TaskType::RemoveSavedDir:
        {
            ui->pushButton_save_clear->setEnabled(true);
            UpdateUI();
            break;
        }
        default: break;
        }

        if (!data.silence) {
            qDebug().noquote() << ui->tableView_logs->addsuccess(data.name);
        } else {
            qDebug().noquote() << data.name;
        }
    } else {
        qCritical().noquote() << ui->tableView_logs->addfailure(data.name);
    }
}

void ISOMode::OnTaskError(const TaskData &data, const QString &contents)
{
    Q_UNUSED(data)
    // qDebug().noquote() << __FUNCTION__ << node.uuid << node.name << ":::" << contents << ":::";

    Q_D(ISOMode);
    QStringList lines = contents.split(QLatin1String("\n"));
    for (const QString &line : qAsConst(lines)) {
        if (line.isEmpty()) {
            continue;
        }
        QRegularExpressionMatch match = d->re_unsquashfs_progress.match(contents);
        if (match.hasMatch()) {
            // QString bar = match.captured(1);
            // QString current = match.captured(2);
            // QString max = match.captured(3);
            QString percent = match.captured(4);
            QString contents = QString(QLatin1String("%1: %2%")).arg(data.name, percent);
            if (!data.silence) {
                qDebug().noquote() << ui->tableView_logs->updateprogress(percent.toInt(), contents);
            } else {
                qDebug().noquote() << contents;
            }
        } else {
            qCritical().noquote() << ui->tableView_logs->adderror(line);
        }
    }
}

void ISOMode::OnTaskOutput(const TaskData &data, const QString &contents)
{
    Q_UNUSED(data)
    // qDebug().noquote() << __FUNCTION__ << node.uuid << node.name << ":::" << contents << ":::";

    Q_D(ISOMode);
    QStringList lines = contents.split(QLatin1String("\n"));
    for (const QString &line : qAsConst(lines)) {
        if (line.isEmpty()) {
            continue;
        }
        QRegularExpressionMatch match = d->re_unsquashfs_progress.match(line);
        if (match.hasMatch()) {
            // QString bar = match.captured(1);
            // QString current = match.captured(2);
            // QString max = match.captured(3);
            QString percent = match.captured(4);
            QString contents = QString(QLatin1String("%1: %2%")).arg(data.name, percent);
            qDebug().noquote() << ui->tableView_logs->updateprogress(percent.toInt(), contents);
            if (!data.silence) {
                qDebug().noquote() << ui->tableView_logs->updateprogress(percent.toInt(), contents);
            } else {
                qDebug().noquote() << contents;
            }
        } else {
            if (!data.silence) {
                qDebug().noquote() << ui->tableView_logs->addnormal(line);
            } else {
                qDebug().noquote() << line;
            }
        }
    }
}
